package net.wanderinghorse.jsonmessage;

import net.wanderinghorse.json.JSONException;
import net.wanderinghorse.json.JSONObject;

/**
 * Response is a JSONMessage subclass which encapsulates
 * a response to a Request object.
 *
 * In the PHP API, responses always get a Request object passed
 * to their ctor. In the Java API, the responses are default-constructed
 * and then their getResponder() member is called to do the request
 * processing. This is to allow us to dynamically load the response
 * classes without having to know if they implement a ctor taking
 * a Request object. Additionally, the Response class presented
 * here is not subclassed to perform response-specific handling. Instead
 * that work is delegated to the ResponseHandler interface. The implication
 * of this is that Response objects do not need to be subclassed to
 * provide implementation behaviours.
 *
 */
public class Response extends JSONMessage
{
    /**
     * Error type values are NOT direct mappings of specific
     * errors to error codes, but are a hint for use with
     * Response.getErrorType(), so that client-side JavaScript code can
     * at least partially determine at what phase of the response
     * process an error happened.
     *
     * See http://code.google.com/p/jsonmessage/wiki/FrameworkConventions
     * for details. 
     */
    public enum ErrTypes {
        /**
         * Indicates that no incoming request was found to dispatch.
         */
        NO_REQUEST_FOUND(-3,"No request data found in environment!"),
        /**
         * Indicates that the conversion of the incoming request from
         * JSON failed.
         */
        FROMJSON_THREW(-2,"From-JSON conversion threw an exception!"),
        /**
         * Indicates that no response handler could be found
         * for an incoming request.
         */
        NO_RESPONSE_FOUND(-1,"No response handler found for request!"),
        /**
         * DO NOT USE THIS ENTRY. It is only here as a placeholder for the 0
         * value.
         *
         * The no-error value, but can also mean that the response
         * contains error information which it set up itself (as opposed
         * to throwing an exception and letting a higher layer catch it).
         *
         */
        NO_ERROR(0,"EOIT: Error of Indeterminate Type!"),
        /**
         * Indicates that the response encapsulates an error caused
         * by another response which threw an exception during
         * its processing of the request.
         */
        RESPONSE_THREW(1,"Response handler threw an exception!"),
        /**
         * Indicates that the response encapsulates an error caused
         * by a Response.toJSON() call which threw.
         */
        TOJSON_THREW(2,"To-JSON conversion threw an exception!"),
        INTERNAL_ERROR(-666,"Some form of internal programming error!")
        ;
        private final int value;
        private final String msg;
        private ErrTypes(final int v, final String msg) {
            this.value = v;
            this.msg = msg;
        }
        public int getValue() {
            return this.value;
        }
        public String getMessage() {
            return msg;
        }

    }
    private int rc = 0;
    private int errType = 0;
    private String resultText = "";
    private String responseTo = null;
    private static final String DefaultWrapperName = "JSONMessage.Response";
    /**
     * Sets up default values for all fields. Error state is set to
     * no-error (resultCode == 0).
     */
    public Response()
    {
        super();
        //this.setWrapperName(this.getClass().getName());
        this.setWrapperName( DefaultWrapperName );
    }

    /**
     * @return The ID set via setRequestId(), or null if no ID
     * has been set.
     */
    public String getRequestId() {
        return this.responseTo;
    }

    /**
     * @param responseTo The ID of the request on who's behalf this
     * response is answering.
     * @return This object.
     */
    public Response setRequestId(String responseTo) {
        this.responseTo = responseTo;
        return this;
    }

    /**
     * @return This message's result code (0 == success).
     */
    public final int getResultCode() {
        return rc;
    }

    /**
     * @param rc This message's result code (0 == success).
     * @return This object.
     */
    public final Response setResultCode(int rc) {
        this.rc = rc;
        return this;
    }

    /**
     * @return This message's error type. See
     *      http://code.google.com/p/jsonmessage/wiki/FrameworkConventions
     *      for details.
     */
    public final int getErrorType() {
        return errType;
    }

    /**
     * @param errType Error type. 0 means non-error.
     * @return This object.
     */
    public final Response setErrorType(int errType) {
        this.errType = errType;
        return this;
    }

    /**
     * @return The message's descriptive result string.
     */
    public final String getResultText() {
        return resultText;
    }

    /**
     *
     * @param resultString The message's descriptive result string.
     * @return This object.
     */
    public final Response setResultText(String resultString) {
        this.resultText = resultString;
        return this;
    }

    /**
     * Sets the error state for this response and clears the payload if rc != 0.
     *
     * @param rc The value for getResultCode()
     * @param errType The value for getErrorType(). If 0, that field is not modified.
     * @param msg The error text, which should briefly describe the problem.
     */
    public final void setErrorInfo( int rc, int errType, String msg )
    {
        this.setResultCode(rc);
        if( 0 != errType ) this.setErrorType(errType);
        this.setResultText(msg);
        if( 0 != rc )
        {
            this.coreProps.payload = null;
        }
    }

    /**
     * Populates dest with Response-specific message fields.
     * This is called by JSONMessage.toJSONObject().
     *
     * Special-case handling:
     *
     * 1) If this.getResultCode() is not 0 then any payload added to dest
     * by the base JSONMessage.toJSONObject() method will be removed because
     * framework conventions specify that a payload has an undefined
     * state if the result code is non-0. This prohibits the response from
     * sending back partial data if it throws/fails after partially populating
     * the payload.
     *
     * 2) If this.getResultCode() is 0 and this.getResultText() is null or
     * empty, a default (but unspecified) "success" value is used for the
     * resultText field.
     *
     * @param dest Current message state, which has (by the time this is called)
     * already been initialized to contain this object's core message properties.
     * @throws JSONException if any of dest's API throws.
     */
    @Override
    protected final void toJSONObject( JSONObject dest ) throws JSONException
    {
        if( 0 != this.errType ) dest.put( JSONMessage.CoreKeys.errorType, this.rc );
        else dest.remove( JSONMessage.CoreKeys.errorType );
        dest.put( JSONMessage.CoreKeys.responseTo, this.responseTo );
        dest.put( JSONMessage.CoreKeys.resultCode, this.rc );
        if( 0 != this.rc ) {
            dest.remove( JSONMessage.CoreKeys.payload );
        }
        if( (0 == this.rc)
                && ((null==this.resultText) || (this.resultText.isEmpty()))
            ) {
            dest.put( JSONMessage.CoreKeys.resultText, "Success" );
        }
        else {
            dest.put( JSONMessage.CoreKeys.resultText, this.resultText );
        }
    }

    /**
     * Overridden to pull out the response-specific properties from src.
     *
     * @param src The input JSON object.
     * @throws JSONException
     */
    @Override
    protected void doFromJSONObject( JSONObject src ) throws JSONException {
        this.setRequestId( src.optString( JSONMessage.CoreKeys.responseTo, null ) );
        this.setResultCode( src.optInt( JSONMessage.CoreKeys.resultCode, 0 ) );
        this.setResultText( src.optString( JSONMessage.CoreKeys.resultText, null ) );
        this.setErrorType( src.optInt( JSONMessage.CoreKeys.errorType, 0 ) );
    }

}